define([
    'app',
    'json5',
    'html2canvas',
    'canvas2image',
    'rtext!views/pages/templates/markdown-editor.demo.md',
    'jspdf',
    'ace-language-tools'
], function(app, JSON5, html2canvas, Canvas2Image, markdownSyntax) {
    app.controller('MarkdownEditorController', function($http, $scope, $compile, $timeout, $showdown, xUtil,
            xDialog, toastr, $location, $routeParams, cfpLoadingBar) {
        var self = this;
        self.code = '';
        self.preview = ($routeParams.preview == 'false' ? false : true);
        self.filepath = '';
        self.overflow = false;
        self.fullScreen = ($routeParams.fullscreen ? true : false);
        self.fullScreen2 = ($routeParams.readonly ? true : false);

        var codeEditor = null,
            mdOptions = $showdown.getOptions();

        var rheader1 = (mdOptions.smoothLivePreview ?
                '^(.+)[ \t]*\n={2,}[ \t]*\n+' : '^(.+)[ \t]*\n=+[ \t]*\n+');
        var rheader2 = (mdOptions.smoothLivePreview ?
                '^(.+)[ \t]*\n-{2,}[ \t]*\n+' : '^(.+)[ \t]*\n-+[ \t]*\n+');
        var rheader = (mdOptions.requireSpaceBeforeHeadingText ?
                '^(#{1,6})[ \t]+(.+?)[ \t]*#*\n+' : '^(#{1,6})[ \t]*(.+?)[ \t]*#*\n+');

        var filename = 'README.md',
            docType = $routeParams.type,
            filepath = null,
            blogid = null,
            modified = null,
            changes = { previous: '', current: '' };

        self.init = function() {
            xUtil.clipboard.copy('#copy', function() {
                return self.code;
            });

            // Code editor
            codeEditor = ace.edit('mdcode-editor', {
                mode: 'ace/mode/markdown',
                theme: 'ace/theme/chrome',
                fontSize: 14,
                wrap: true,
                showPrintMargin: false,
                autoScrollEditorIntoView: true,
                enableBasicAutocompletion: true,
                enableSnippets: true,
                enableLiveAutocompletion: true
            });
            codeEditor.resize();
            codeEditor.on('change', function(e) {
                $timeout(function() {
                    self.code = codeEditor.getValue();
                })
            });

            // Code editor tools
            $('#mdcode-editor .ace_scroller').append(
                    $compile($('#mdcode-editor-tools').html())($scope)
                );
            $('#mdcode-editor .x-editor-tool').draggable({
                cursor: 'move',
                handle: '.btn-draggable',
                containment: 'parent',
                delay: 200,
                opacity: 0.60,
                drag: function() {
                    $(this).css('right', 'auto');
                }
            }).mousedown(function(e) {
                return false;
            });

            (function() {
                var jqMarkdown = $('.x-markdown');
                $scope.$watchGroup(['vm.fullScreen2', function() {
                    return jqMarkdown.prop('scrollHeight');
                }], function() {
                    $timeout(function() {
                        self.overflow = jqMarkdown.prop('clientHeight') < jqMarkdown.prop('scrollHeight')
                    });
                });
            })();

            var jqGotoTop = $('#mdcode-editor .x-editor-tool.x-goto-top');

            var timeout = null,
                syncScroll = function() {
                    var startRow = codeEditor.getFirstVisibleRow(),
                        endRow = codeEditor.getLastVisibleRow(),
                        content = '',
                        headerId = null,
                        headerIds = null;
                        headerIdCount = 0;
                    for (var row = startRow; row <= endRow; row++) {
                        content = codeEditor.getSession().getLines(startRow, row).join('\n');
                        headerIds = getHeaderIds(content.trim());
                        if (!_.isEmpty(headerIds)) {
                            headerId = headerIds[0];
                            // lookup same previous header
                            if (startRow > 1) {
                                content = codeEditor.getSession().getLines(0, startRow - 1).join('\n');
                                headerIds = getHeaderIds(content.trim(), true);
                                headerIdCount = _.size(_.filter(headerIds, function(id) {
                                        return id === headerId;
                                    }));
                                if (headerIdCount > 0) {
                                    headerId += '-' + headerIdCount;
                                }
                            }
                            // Scroll preview
                            var jqHeader = $('#' + headerId);
                            if (jqHeader.length > 0) {
                                $('.x-markdown').stop(true).animate({
                                    scrollTop: $('.x-markdown').scrollTop() + jqHeader.position().top
                                }, 200);
                                break;
                            }
                        }
                    }
                };
            codeEditor.getSession().on('changeScrollTop', function(scrollTop) {
                if (scrollTop > 50) {
                    jqGotoTop.removeClass('hidden');
                } else {
                    jqGotoTop.addClass('hidden');
                }
                clearTimeout(timeout);
                // Match and scroll
                timeout = setTimeout(syncScroll, 200);
            });
            // Full screen
            $scope.$watchGroup(['vm.preview', 'vm.fullScreen'], function() {
                setTimeout(function() {
                    codeEditor.resize();
                    $('#mdcode-editor .x-editor-tool').css('left', '').css('top', '').css('right', '');
                }, 50);
            });

            if (docType == 'blogdoc') {
                blogid = $routeParams.params;
                // Load blog
                $http.get('/blog/articles/' + blogid + '.md').then(function(response) {
                    if (response.status == 200) {
                        codeEditor.setValue(response.data, -1);
                        codeEditor.resize();
                        // Get blog title
                        $http.get('/blog/blogs.json').then(function(response) {
                            var articles = response.data.articles;
                            for (var i = 0; i < articles.length; i++) {
                                if (articles[i].id == blogid) {
                                    filename = articles[i].title.replace(/[\?\*:"<>\\\/\|]+/g, '').trim() + '.md';
                                    modified = articles[i].modified;
                                    break;
                                }
                            }
                        });
                    }
                });
            } else if (docType == 'mockdoc') {
                filepath = $routeParams.params;
                // Load mock
                $http({
                    method: 'POST',
                    url: '/' + filepath + '?origin=true',
                    transformResponse: function(response) {
                        return response;
                    }
                }).then(function(response) {
                    if (response.status == 200) {
                        filename = filepath.substring(filepath.lastIndexOf('/') + 1);
                        if (/\.(json|xml)$/.test(filepath)) {
                            loadMockDocument(filepath, response.data);
                        } else {
                            // load markdown file
                            self.filepath = filepath;
                            modified = response.headers('x-modified');
                            changes.previous = changes.current = response.data;
                            codeEditor.setValue(response.data, -1);
                            codeEditor.resize();
                        }
                    }
                });
            } else {
                // Load syntax of markdown
                $timeout(function() {
                    codeEditor.setValue(markdownSyntax, -1);
                    codeEditor.resize();
                });
            }

            // Preview button fixed
            $('.x-markdown').scroll(function() {
                $(this).find('>.x-fixed-btn').css('top', (this.scrollTop + 2) + 'px');
            });
        }

        function loadMockDocument(filepath, content) {
            var extname = filepath.substring(filepath.lastIndexOf('.'));
            filename = xUtil.regex.keyMatch(content, '标题').trim();

            // Load mock document
            var docItems = xUtil.mockdoc.parse(content);
            content = '';
            _.forEach(docItems, function(docValue, docItem) {
                docValue = (docValue || '').trim();
                if (content != '') {
                    content += '\n\n';
                }
                content += '### ' + docItem + '：';
                if (docItem == 'Headers') {
                    var headers = JSON5.parse(docValue) || {};
                    headers['Content-Type'] = 'application/' + (extname == '.xml' ? 'xml' : 'json');
                    _.forEach(headers, function(value, name) {
                        content += '\n- **' + name + ':** ' + value;
                    });
                } else if (docItem == 'Query' || docItem == '入参' || docItem == '出参') {
                    if (extname == '.xml') {
                        content += '\n```xml\n' + docValue + '\n```';
                    } else {
                        // TODO Mock.mock(docValue)
                        content += '\n```json\n' + docValue + '\n```';
                    }
                } else if ((docItem == '备注' || docItem == '错误') && docValue != '') {
                    var items = docValue.split(/\\n/);
                    _.forEach(items, function(item) {
                        content += '\n- ' + item.trim();
                    });
                } else {
                    content += docValue;
                }
            });

            codeEditor.setValue(content, -1);
            codeEditor.resize();
        }

        function getHeaderIds(content, global) {
            var headerId = null,
                headerIds = [];

            var rxHeader1 = new RegExp(rheader1, 'm' + (global ? 'g' : '')),
                rxHeader2 = new RegExp(rheader2, 'm' + (global ? 'g' : '')),
                rxHeader = new RegExp(rheader, 'm' + (global ? 'g' : ''));

            if (rxHeader1.test(content)) {
                content = content.replace(rxHeader1, function (wholeMatch, m1) {
                    headerId = getHeaderId(m1);
                    headerIds.push(headerId);
                    return '';
                });
            } else if (rxHeader2.test(content)) {
                content = content.replace(rxHeader2, function (matchFound, m1) {
                    headerId = getHeaderId(m1);
                    headerIds.push(headerId);
                    return '';
                });
            } else if (rxHeader.test(content)) {
                content = content.replace(rxHeader, function (wholeMatch, m1, m2) {
                    headerId = getHeaderId(m2);
                    headerIds.push(headerId);
                    return '';
                });
            }

            if (global && headerId != null) {
                headerIds = _.flatten(headerIds, getHeaderIds(content, global));
            }

            return headerIds;
        }

        function getHeaderId(title) {
            if (mdOptions.ghCompatibleHeaderId) {
                title = title
                  .replace(/ /g, '-')
                  .replace(/&amp;/g, '')
                  .replace(/¨T/g, '')
                  .replace(/¨D/g, '')
                  .replace(/[&+$,\/:;=?@"#{}|^¨~\[\]`\\*)(%.!'<>]/g, '')
                  .toLowerCase();
            } else if (mdOptions.rawHeaderId) {
                title = title
                  .replace(/ /g, '-')
                  .replace(/&amp;/g, '&')
                  .replace(/¨T/g, '¨')
                  .replace(/¨D/g, '$')
                  .replace(/["']/g, '-')
                  .toLowerCase();
            } else {
                title = title
                  .replace(/[^\w]/g, '')
                  .toLowerCase();
            }

            if (mdOptions.rawPrefixHeaderId) {
                var prefix = '';
                if (_.isString(mdOptions.prefixHeaderId)) {
                    prefix = mdOptions.prefixHeaderId;
                } else if (mdOptions.prefixHeaderId === true) {
                    prefix = 'section-';
                } else {
                    prefix = '';
                }
                title = prefix + title;
            }

            return title;
        }

        self.open = function(file) {
            if (file != null) {
                if (/\.md$/i.test(file.name)) {
                    filename = file.name;
                    // open file
                    xUtil.file.open(file).then(function(response) {
                        codeEditor.setValue(response.data, -1);
                        codeEditor.resize();
                        self.gotoTop();
                        $('.x-markdown').stop(true).scrollTop(0);
                    });
                } else {
                    xDialog.alert('提示信息', '您打开的不是Markdown文件！');
                }
            }
        }

        self.save = function() {
            changes.current = codeEditor.getValue();
            if (/^\s*$/.test(changes.current)) {
                toastr.error('文件内容不可以为空！');
            } else if (changes.previous == changes.current) {
                toastr.warning('文件内容没有任何修改！');
            } else {
                // Save file
                xDialog.confirm('确认信息', '您确定要保存文件吗？').then(function(yes) {
                    if (yes) {
                        var module = filepath.substring(0, filepath.indexOf('/'));
                        var dataFile = filepath.substring(filepath.indexOf('/') + 1);
                        $http.post('/mock/data/save', {
                            module: module,
                            type: 'file',
                            folder: '',
                            file: dataFile,
                            data: changes.current,
                            modified: modified
                        }).then(function(response) {
                            if (response.status == 200) {
                                changes.previous = changes.current;
                                // check and reload
                                setTimeout(function() {
                                    if (response.data == 'required') {
                                        xDialog.alert('提示信息', '数据模块【' + module + '】不存在！');
                                    } else if (response.data == 'expired') {
                                        xDialog.alert('提示信息', '文件已被更新，请刷新后再保存！');
                                    } else if (response.data) {
                                        // reload committed data
                                        $http({
                                            method: 'POST',
                                            url: '/' + filepath + '?origin=true',
                                            transformResponse: function(_response) {
                                                return _response;
                                            }
                                        }).then(function(_response) {
                                            if (_response.status == 200) {
                                                modified = _response.headers('x-modified');
                                                if (codeEditor.getValue() != _response.data) {
                                                    codeEditor.setValue(_response.data, -1);
                                                }
                                            }
                                        });
                                        //modified = response.data;
                                        toastr.success('文件已保存！');
                                    }
                                }, 500);
                            }
                        });
                    }
                });
            }
        }

        self.saveAs = function(docType) {
            if (docType == 'html') {
                var template = [
                        '<!DOCTYPE html>',
                        '<html lang="zh-CN">',
                        '  <head>',
                        '    <meta charset="UTF-8">',
                        '    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css">',
                        '    <link rel="stylesheet" href="https://cdn.bootcss.com/highlight.js/9.18.1/styles/googlecode.min.css">',
                        '    <%_.each(styles, function(style) {%><style><%=style%></style><%})%>',
                        '    <title><%=title%></title>',
                        '  </head>',
                        '  <body class="container">',
                        '    <div class="row">',
                        '      <div class="col-lg-offset-1 col-lg-10 col-md-12">',
                        '        <div class="x-markdown"><%=content%></div>',
                        '      </div>',
                        '    </div>',
                        '  </body>',
                        '</html>'
                    ].join('\n');
                var parameters = {
                    title: filename,
                    styles: [],
                    content: $('.x-markdown>p').prop('outerHTML')
                };
                $http.get('/app/css/markdown.css').then(function(response) {
                    if (response.status == 200) {
                        parameters.styles.push(response.data);
	                    var content = _.template(template)(parameters);
	                    xUtil.file.save(filename + '.html', content);
                    }
                });
            } else if (docType == 'png' || docType == 'pdf') {
                // Render markdown document
                var mdDoc = $('<div class="x-markdown"></div>').css({
                            'position': 'fixed',
                            'top': '-1000px',
                            'left': '-1000px'
                        })
                        .append($('.x-markdown>p').clone(false))
                        .appendTo(document.body)[0];
                if (docType == 'pdf') {
                    $(mdDoc).css('width', '595.28pt').css('padding', '30px 20px');
                } else {
                    $(mdDoc).css('width', '900px').css('padding', '20px 20px 10px 20px');
                }

                cfpLoadingBar.start();
                // Convert html to canvas
                html2canvas(mdDoc, {
                    allowTaint: true,
                    //scale: 2,
                    useCORS: true,
                    removeContainer: true
                }).then(function(canvas) {
                    filename = filename.replace(/\.md$/, '');
                    if (docType == 'pdf') {
                        var imageData = canvas.toDataURL('image/png', 1.0);
                        // A4 page size: [595.28pt, 841.89pt]
                        // 1 Point = 1.333333 Pixel
                        // 1 px =   0.75 point
                        var pdfDoc = new jsPDF('p', 'pt', 'a4');
                        var position = 0;
                        var pageHeight = 841.89;
                        var leftHeight = mdDoc.offsetHeight * 0.75;
                        do {
                            pdfDoc.addImage(imageData, 'PNG', 0, position, mdDoc.offsetWidth * 0.75,
                                    mdDoc.offsetHeight * 0.75);
                            leftHeight -= pageHeight;
                            position -= pageHeight;
                            if (leftHeight > 0) {
                                pdfDoc.addPage();
                            }
                        } while (leftHeight > 0);
                        pdfDoc.save(filename + '.pdf');
                    } else {
                        Canvas2Image.saveAsPNG(canvas, filename, mdDoc.offsetWidth, mdDoc.offsetHeight);
                    }
                    $(mdDoc).remove();
                    cfpLoadingBar.complete();
                });
            } else {
                xUtil.file.save(filename + (/\.md$/.test(filename) ? '' : '.md'), codeEditor.getValue());
            }
        }

        // Insert markdown code
        self.insert = function(type, value) {
            var objWrap = null,
                text = codeEditor.getSession().getTextRange(codeEditor.getSelectionRange());
            switch (type) {
                case 'header':
                    var mark = _.map(_.range(value), function() {
                            return '#';
                        }).join('');
                    objWrap = {
                        content: mark + ' ' + text,
                        cursor: (mark + ' ').length
                    };
                    break;
                case 'bold':
                    objWrap = { content: '**' + text + '**', cursor: 2 };
                    break;
                case 'italic':
                    objWrap = { content: '*' + text + '*', cursor: 1 };
                    break;
                case 'list':
                    objWrap = {
                        content: _.map(text.split('\n'), function(line) {
                                return '- ' + line;
                            }).join('\n'),
                        cursor: 2
                    };
                    break;
                case 'table':
                    xDialog.open().showTable().then(function(table) {
                        var column = '|' + _.map(_.range(table.columns), function() {
                                return '  ';
                            }).join('|') + '|';
                        var thead = column,
                            divider = '|' + _.map(_.range(table.columns), function() {
                                    return '---';
                                }).join('|') + '|',
                            tbody = _.map(_.range(table.rows - 1), function() {
                                    return column;
                                }).join('\n');
                        codeEditor.insert([thead, divider, tbody].join('\n'));
                    });
                    break;
                case 'link':
                    objWrap = { content: '[超链接](http:// "")', cursor: 13 };
                    text = '';
                    break;
                case 'quote':
                    objWrap = {
                        content: _.map(text.split('\n'), function(line) {
                                return '>' + line;
                            }).join('\n'),
                        cursor: 1
                    };
                    break;
                case 'image':
                    xDialog.open().showImage().then(function(image) {
                        codeEditor.insert('![图片](' + image.url + ' "' + image.name + '")');
                    });
                    break;
                case 'emoji':
                    xDialog.open().showEmoji().then(function(emoji) {
                        codeEditor.insert(emoji);
                    });
                    break;
                case 'code':
                    if (value == 'line') {
                        objWrap = { content: '`' + text + '`', cursor: 1 };
                    } else if (value == 'block') {
                        xDialog.open().editCode({ text: text }).then(function(data) {
                            codeEditor.insert('```' + data.language + '\n' + data.code + '\n```');
                        });
                    } else if (value == 'flow') {
                        objWrap = {
                            content: [
                                '```flow',
                                'st=>start: Start',
                                'op=>operation: Your Operation',
                                'cond=>condition: Yes or No?',
                                'e=>end',
                                'st->op->cond',
                                'cond(yes)->e',
                                'cond(no)->op',
                                '```'
                            ].join('\n'),
                            cursor: 0
                        };
                    } else if (value == 'inline') {
                        objWrap = { content: '$x = {-b \\pm \\sqrt{b^2-4ac} \\over 2a}$', cursor: 0 };
                    } else if (value == 'display') {
                        objWrap = {
                            content: [
                                '$$',
                                '\\begin{pmatrix} a & b \\\\ c & d \\\\ \\end{pmatrix} \\quad',
                                '\\begin{bmatrix} a & b \\\\ c & d \\\\ \\end{bmatrix} \\quad',
                                '\\begin{Bmatrix} a & b \\\\ c & d \\\\ \\end{Bmatrix} \\quad',
                                '\\begin{vmatrix} a & b \\\\ c & d \\\\ \\end{vmatrix} \\quad',
                                '\\begin{Vmatrix} a & b \\\\ c & d \\\\ \\end{Vmatrix}',
                                '$$'
                            ].join('\n'),
                            cursor: 0
                        };
                    }
                    break;
                default:
                    break;
            }
            // Insert wrap code
            if (objWrap) {
                var objCursor = codeEditor.selection.getCursor();
                codeEditor.insert(objWrap.content);
                if (_.isEmpty(text)) {
                    codeEditor.moveCursorTo(objCursor.row, objCursor.column + objWrap.cursor);
                }
                codeEditor.focus();
            }
        }

        // Goto top of editor
        self.gotoTop = function() {
            codeEditor.scrollToLine(0);
        }

        self.callback = function(height, width, refresh) {
            $('.x-view-scope').css('height', height + 'px');
            $('.x-view-scope .x-editor pre').css('min-height', height + 'px');
            // Toolbar
            if (refresh) {
                $('.navbar-form').append($compile($('#opt-toolbar').html())($scope));
            }
        }

        self.publish = function() {
            var jqMarkdown = $('.x-markdown>p').clone();
            // Snapshot (svg excepted)
            jqMarkdown.find('svg').remove();
            var snapshot = jqMarkdown.text().replace(/\s+/g, ' ').trim();
            if (snapshot.length > 200) {
                snapshot = snapshot.substring(0, 197) + '...';
            }
            xDialog.open().publishBlog({
                blogid: blogid,
                content: self.code,
                snapshot: snapshot,
                modified: modified
            }).then(function(blog) {
                if (blog.id) {
                    filename = blog.title + '.md';
                    modified = blog.modified;
                    if (blogid == null) {
                        blogid = blog.id;
                        self.filepath = '';
                        $location.path('/markdown-editor/blogdoc/' + blogid, false).replace();
                    }
                }
            });
        }

        self.fnPreview = function() {
            self.preview = !self.preview;
            $location.search('preview', (self.preview ? 'true' : 'false')).replace();
        }

        self.fnFullScreen = function() {
            self.fullScreen = !self.fullScreen;
            $location.search('fullscreen', (self.fullScreen ? 'true' : null)).replace();
        }

        self.fnReadonlyAndFullScreen = function() {
            self.fullScreen2 = !self.fullScreen2;
            $location.search('readonly', (self.fullScreen2 ? 'true' : null)).replace();
        }

        self.clear = function() {
            codeEditor.setValue(''/*, -1*/);
            codeEditor.resize();
        }
    });
});
